#!/usr/bin/env python2.5
#
# Upload and manage Blobstore objects within Google App Engine and show on a map
#
# Kelsey Jordahl
# Time-stamp: <Sat Mar 19 14:41:52 EDT 2011>

# Based in part on http://blog.notdot.net/2010/03/Implementing-a-dropbox-service-with-the-Blobstore-API-Part-1
# https://gist.github.com/348025/d4fc873bda4a650210559f881738cc55a667ff2a
# by Nick Johnson
# other pieces from the Google App Engine documentation
# including http://code.google.com/appengine/articles/rpc.html for AJAX RPCs
# and http://code.google.com/apis/maps/documentation/javascript/overlays.html#CustomOverlays for the map overlay example

from django.utils import simplejson
from google.appengine.api import users, images
from google.appengine.ext import db, blobstore, webapp
from google.appengine.ext.webapp import util, template, blobstore_handlers
#from libtiff import TIFFfile, TIFFimage

import os, logging

class FileInfo(db.Model):
  blob = blobstore.BlobReferenceProperty(required=True)
  thumb = blobstore.BlobReferenceProperty(required=False)
  uploaded_by = db.UserProperty(required=True)
  uploaded_at = db.DateTimeProperty(required=True, auto_now_add=True)
  north = db.FloatProperty(required=False)
  south = db.FloatProperty(required=False)
  east = db.FloatProperty(required=False)
  west = db.FloatProperty(required=False)

class BaseHandler(webapp.RequestHandler):
  def render_template(self, file, template_args):
    path = os.path.join(os.path.dirname(__file__), "templates", file)
    self.response.out.write(template.render(path, template_args))

class ListBlobHandler(BaseHandler):
  """
  List the available images in the Blobstore

  This really could use a template file
  """
  @util.login_required
  def get(self):
    self.response.out.write('<title>Blobgeo</title>')
    self.response.out.write('<h1>Blobgeo: Put your pictures on the map</h1>')
    self.response.out.write('<p>Upload and plot images as a Google Map overlay.  ')
    self.response.out.write('Uses <a href="http://appengine.google.com">Google App Engine</a> and the <a href="http://code.google.com/appengine/docs/python/blobstore/">Blobstore</a>, serving images through the built-in <a href="http://code.google.com/appengine/docs/python/images/functions.html#Image_get_serving_url">high-performance image serving system</a>.<br>')
    self.response.out.write('File bounds (West/East/South/North) must be set manually after upload in order to plot.  The bounds can be changed later if desired.  The map projection of images should be <a href="http://docs.openlayers.org/library/spherical_mercator.html">spherical Mercator</a> to match that used by Google Maps (although for small regions unprojected geographical coordinates may be acceptible).</p>')
    self.response.out.write('<p><a href="http://mysite.verizon.net/kajordahl/">Kelsey Jordahl</a></p><hr><h2>Available images:</h2>')
    for file in FileInfo.all():
      try:
        url = images.get_serving_url(str(file.blob.key()), size=64)
      except images.LargeImageError:
        img_url = 'none.png'
        logging.error('Image too large!')
      id = file.key().id()
      file_info = FileInfo.get_by_id(long(id))
      self.response.out.write('<a href=/file/%s><img src=%s></a>ID: %s, Filename: %s' % (id, url, id, file_info.blob.filename))
      if file_info.north:
        self.response.out.write('  <a href=/map/%s>Map it!</a>' % id)
      self.response.out.write('</br>')
    self.response.out.write('Upload a new file:')
    self.response.out.write('<form method="POST" action="%s" enctype="multipart/form-data">' % blobstore.create_upload_url('/upload'))
    self.response.out.write('<input type="file" name="file" /><br />')
    self.response.out.write('<input type="submit" value="Upload" />')
    self.response.out.write('</form>')
      
class FileUploadFormHandler(BaseHandler):
  @util.login_required
  def get(self):
    self.render_template("upload.html", {
        'form_url': blobstore.create_upload_url('/upload'),
        'logout_url': users.create_logout_url('/'),
    })


class FileUploadHandler(blobstore_handlers.BlobstoreUploadHandler):
  def post(self):
    blob_info = self.get_uploads()[0]
    if not users.get_current_user():
      blob_info.delete()
      self.redirect(users.create_login_url("/"))
      return

    logging.info(dir(blob_info))
    file_info = FileInfo(blob=blob_info.key(),
                         uploaded_by=users.get_current_user())
    db.put(file_info)
    self.redirect("/file/%d" % (file_info.key().id(),))


class MapHandler(BaseHandler):
  def get(self, file_id):
    file_info = FileInfo.get_by_id(long(file_id))
    try:
      img_url = images.get_serving_url(str(file_info.blob.key()))
    except images.LargeImageError:
      logging.error('Image too large!')
      img_url = 'none.png'
    if (file_info.north):
      logging.info('URL: %s' % img_url)
      lat_center = (file_info.north + file_info.south) / 2
      lon_center = (file_info.east + file_info.west) / 2
      self.render_template("map.html", {
        'img_url':  img_url,
        'north': file_info.north,
        'south': file_info.south,
        'west': file_info.west,
        'east': file_info.east,
        'lat_center': lat_center,
        'lon_center': lon_center,
        })
    else:
      self.response.out.write('No file bounds set!</br>')
      self.response.out.write('North: %s</br>' % file_info.north)
      self.response.out.write('<a href=/file/%d>Go back to set geographic bounds</a>' % (file_info.key().id(),))

class ThumbHandler(BaseHandler):
  def get(self, file_id):
    file_info = FileInfo.get_by_id(long(file_id))
    try:
      img_url = images.get_serving_url(str(file_info.blob.key()), size=256)
    except images.LargeImageError:
      logging.error('Image too large!')
      img_url = 'none.png'
    logging.info('URL: %s' % img_url)

class FileInfoHandler(BaseHandler):
  def get(self, file_id):
    file_info = FileInfo.get_by_id(long(file_id))
    try:
      img_url = images.get_serving_url(str(file_info.blob.key()))
    except images.LargeImageError:
      logging.error('Image too large!')
      img_url = 'none.png'
    logging.info('URL: %s' % img_url)
    if not file_info:
      self.error(404)
      return
    self.render_template("info.html", {
      'file_info': file_info,
      'img_url': img_url,
      'file_id': file_id,
      'north': file_info.north,
      'south': file_info.south,
      'west': file_info.west,
      'east': file_info.east,
      'logout_url': users.create_logout_url('/'),
    })

class RPCHandler(webapp.RequestHandler):
    """ Allows the functions defined in the RPCMethods class to be RPCed."""
    def __init__(self):
        webapp.RequestHandler.__init__(self)
        self.methods = RPCMethods()

    def get(self):
        func = None

        action = self.request.get('action')
        if action:
            if action[0] == '_':
                self.error(403) # access denied
                return
            else:
                func = getattr(self.methods, action, None)

        if not func:
            self.error(404) # file not found
            return

        args = ()
        while True:
            key = 'arg%d' % len(args)
            val = self.request.get(key)
            if val:
                args += (simplejson.loads(val),)
            else:
                break
        result = func(*args)
        self.response.out.write(simplejson.dumps(result))

class FileDownloadHandler(blobstore_handlers.BlobstoreDownloadHandler):
  def get(self, file_id):
    file_info = FileInfo.get_by_id(long(file_id))
    if not file_info or not file_info.blob:
      self.error(404)
      return
    self.send_blob(file_info.blob, save_as=True)

class RPCMethods:
    """ Defines the methods that can be RPCed.
    NOTE: Do not allow remote callers access to private/protected "_*" methods.
    """
    def Change(self, *args):
      """Actually change the values for file geographic bounds
      """
      file_id = args[0]
      file_info = FileInfo.get_by_id(long(file_id))
      file_info.west = float(args[1])
      file_info.east = float(args[2])
      file_info.south = float(args[3])
      file_info.north = float(args[4])
      logging.info('ID: %s' % file_id)
      logging.info('Size: %s' % file_info.blob.size)
      logging.info('By: %s' % file_info.uploaded_by)
      logging.info('West: %s' % file_info.west)
      logging.info('East: %s' % file_info.east)
      logging.info('South: %s' % file_info.south)
      logging.info('North: %s' % file_info.north)
      db.put(file_info)
      return file_info.north # I don't use the return value for anything, just checking
      
application = webapp.WSGIApplication([
    ('/', ListBlobHandler),
    ('/upload', FileUploadHandler),
    ('/list.*', ListBlobHandler),
    ('/map/([0-9]+)', MapHandler),
    ('/rpc', RPCHandler),
    ('/file/([0-9]+)', FileInfoHandler),
    ('/file/([0-9]+)/download', FileDownloadHandler),
])


def main():
  util.run_wsgi_app(application)


if __name__ == '__main__':
  main()
